/* @flow */

import Dep from './dep'
import VNode from '../vdom/vnode'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 */
export let shouldObserve: boolean = true

export function toggleObserving(value: boolean) {
  shouldObserve = value
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  value: any; // 观测对象 也就是 vue 实例的 data
  dep: Dep; // 依赖对象
  // 实例计数器
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) {
    this.value = value
    this.dep = new Dep()
    console.log(value);
    this.dep.key = value;
    this.vmCount = 0
    // 给 data 加上 __ob__ 属性进行缓存 
    // 下次这个data进来做响应式处理的时候就可以直接取到__ob__ 第120行
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk(obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment(target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment(target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe(value: any, asRootData: ?boolean): Observer | void {
  // 如果data不是对象 或者 data 是虚拟dom的实例 直接返回
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果 data 中有__ob__ 且它是 Observer 的实例 直接赋值给ob
  // __ob__ 就是Observer对象 相对于做一个缓存
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if ( // 判断是否可以进行响应式处理
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue // 这个_isVue就是前面init组件的时候加上去的哦
  ) {
    // 如果可以的话 创建一个 Observer 对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * Define a reactive property on an Object.
 */
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean // 如果是true 只监听这个对象的第一层数据 如果是false 那么所有的都要做成响应式
) {
  // 这个dep是收集依赖用的 也就是收集页面上所有用到当前 key 的 watcher
  const dep = new Dep()
  dep.key = key;

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 获取用户自定义的get和set 有可能传入的data里有用户自己写的get set
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 判断是否是深层监听 如果是的话 那么如果 val 也是对象 还要进行处理
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true, // 可枚举
    configurable: true, // 可配置
    get: function reactiveGetter() {
      // 如果用户设置了自己的 get 那么调用这个get获取值 否则 直接用本身的值
      const value = getter ? getter.call(obj) : val
      if (Dep.target) { // 如果存在当前依赖目标即 watcher 对象， 则建立依赖
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      // 判断用户自己是否设置了get 如果有的话 先执行用户设置的set
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 判断新旧值是否相同 如果相同直接return
      // 后面这个 (newVal !== newVal && value !== value) 是处理 NaN 的特殊情况
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      // 如果用户只写了get 没写set说明是只读的 直接返回
      if (getter && !setter) return
      if (setter) { // 如果用户自己定义了set 执行用户定义的 set
        setter.call(obj, newVal)
      } else { // 否则直接赋值
        val = newVal
      }
      // 如果新的值是对象的话 而且是深层处理 那么把对象里的值也要转换成get set
      childOb = !shallow && observe(newVal)
      // 发布通知 通知所有依赖这个 key 的watcher 执行 update 方法更新视图
      dep.notify()
    }
  })
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) { // 如果 set 的是一个数组的话
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val) // 调用vue修改的数组的 splice 方法修改这个值 vue 修改的 splice 方法会触发更新
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val // 如果要 set 的 key 在 target 中已经存在 直接重新赋值
    return val
  }
  // ob 是判断 target 是不是响应式对象 响应式对象都有这个 ob 属性 存的是该对象的Observer对象
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) { // 如果没有 ob 属性 说明 target 不是响应式对象，那么他的key也更没有必要是响应式了
    target[key] = val
    return val
  }
  // 如果 target 是响应式对象 并且 key 不存在 那么调用 defineReactive 把 key 设置为 当前 target 的一个响应式成员
  defineReactive(ob.value, key, val)
  ob.dep.notify() // 触发 依赖于 target 的所有 watcher 更新
  return val
}

/**
 * Delete a property and trigger change if necessary.
 */
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray(value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
